package com.aner.tester.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.aner.tester.config.AnerTesterConfig;
import com.aner.tester.constant.Constant;
import com.aner.tester.utils.text.StringUtils;
import org.junit.Before;
import org.slf4j.Logger;
import com.aner.tester.utils.LoggerUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
 * the base class of test controller
 *
 * @author aner
 * @version 1.0
 * @date 2020/12/13 8:06
 */
@AutoConfigureMockMvc
public class BaseControllerTest {

    private MockMvc mockMvc;

    private final Logger logger = LoggerUtils.getLogger(this.getClass());


    @Autowired
    private WebApplicationContext webApplicationContext;
    

    @Before
    public void init() {
        AnerTesterConfig.me().init();
        DefaultMockMvcBuilder defaultMockMvcBuilder = MockMvcBuilders.webAppContextSetup(webApplicationContext);
        if (AnerTesterConfig.me().isLogin()) {
            // set security authentication
            defaultMockMvcBuilder.apply(SecurityMockMvcConfigurers.springSecurity());
        }
        mockMvc = defaultMockMvcBuilder.build();
    }

    /**
     * get with form-data data
     * @param url
     * @param params
     */
    public void get(String url, String params) {
        try {
            MockHttpServletRequestBuilder builder;
            // PathVariable
            if (isUrlTemplate(url)) {
                builder = MockMvcRequestBuilders.get(url, convertJsonToArray(params));
            } else {
                builder = MockMvcRequestBuilders.get(url)
                        // set request param
                        .params(convertJsonToMap(params));
            }
            RequestBuilder requestBuilder = buildHeaderAuthentication(builder)

                    // set request content type
                    .contentType(formData());
            String result = performMockMvc(requestBuilder).getContentAsString();
            doResult(result);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * post with form-data data
     *
     * @param url
     * @param params
     */
    public void post(String url, String params) {
        try {
            MockHttpServletRequestBuilder builder;
            // PathVariable
            if (isUrlTemplate(url)) {
                builder = MockMvcRequestBuilders.post(url, convertJsonToArray(params));
            } else {
                builder = MockMvcRequestBuilders.post(url)
                        // set request param
                        .params(convertJsonToMap(params));
            }
            RequestBuilder requestBuilder = buildHeaderAuthentication(builder)
                    // set request content type
                    .contentType(formData());
            String result = performMockMvc(requestBuilder).getContentAsString();
            doResult(result);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * post with payload data
     *
     * @param url
     * @param param
     */
    public void postBody(String url, String param) {

        try {
            RequestBuilder requestBuilder = buildHeaderAuthentication(MockMvcRequestBuilders.post(url))
                    // 设置body内容
                    .content(param)
                    // set request content type
                    .contentType(payload());
            String result = performMockMvc(requestBuilder).getContentAsString();
            doResult(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * put request with form-data data
     *
     * @param url
     * @param params
     */
    public void put(String url, String params) {
        try {
            MockHttpServletRequestBuilder builder;
            // PathVariable
            if (isUrlTemplate(url)) {
                builder = MockMvcRequestBuilders.put(url, convertJsonToArray(params));
            } else {
                builder = MockMvcRequestBuilders.put(url)
                        // set request param
                        .params(convertJsonToMap(params));
            }
            RequestBuilder requestBuilder = buildHeaderAuthentication(builder)
                    // set request content type
                    .contentType(formData());
            String result = performMockMvc(requestBuilder).getContentAsString();
            doResult(result);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * put request with payload data
     *
     * @param url
     * @param param
     */
    public void putBody(String url, String param) {

        try {
            RequestBuilder requestBuilder = buildHeaderAuthentication(MockMvcRequestBuilders.put(url))
                    // 设置body内容
                    .content(param)
                    // set request content type
                    .contentType(payload());
            String result = performMockMvc(requestBuilder).getContentAsString();
            doResult(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * delete with form-data data
     *
     * @param url
     * @param params
     */
    public void delete(String url, String params) {
        try {
            MockHttpServletRequestBuilder builder;
            // PathVariable
            if (isUrlTemplate(url)) {
                builder = MockMvcRequestBuilders.delete(url, convertJsonToArray(params));
            } else {
                builder = MockMvcRequestBuilders.delete(url)
                        // set request param
                        .params(convertJsonToMap(params));
            }
            RequestBuilder requestBuilder = buildHeaderAuthentication(builder)
                    // set request content type
                    .contentType(formData());
            String result = performMockMvc(requestBuilder).getContentAsString();
            doResult(result);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * delete with payload data
     *
     * @param url
     * @param param
     */
    public void deleteBody(String url, String param) {

        try {
            RequestBuilder requestBuilder = buildHeaderAuthentication(MockMvcRequestBuilders.delete(url))
                    // 设置body内容
                    .content(param)
                    // set request content type
                    .contentType(payload());
            String result = performMockMvc(requestBuilder).getContentAsString();
            doResult(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * patch with form-data data
     *
     * @param url
     * @param params
     */
    public void patch(String url, String params) {
        try {
            MockHttpServletRequestBuilder builder;
            // PathVariable
            if (isUrlTemplate(url)) {
                builder = MockMvcRequestBuilders.patch(url, convertJsonToArray(params));
            } else {
                builder = MockMvcRequestBuilders.patch(url)
                        // set request param
                        .params(convertJsonToMap(params));
            }
            RequestBuilder requestBuilder = buildHeaderAuthentication(builder)
                    // set request content type
                    .contentType(formData());
            String result = performMockMvc(requestBuilder).getContentAsString();
            doResult(result);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * patch with payload data
     *
     * @param url
     * @param param
     */
    public void patchBody(String url, String param) {

        try {
            RequestBuilder requestBuilder = buildHeaderAuthentication(MockMvcRequestBuilders.patch(url))
                    // 设置body内容
                    .content(param)
                    // set request content type
                    .contentType(payload());
            String result = performMockMvc(requestBuilder).getContentAsString();
            doResult(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * options with form-data data
     *
     * @param url
     * @param params
     */
    public void options(String url, String params) {
        try {
            MockHttpServletRequestBuilder builder;
            // PathVariable
            if (isUrlTemplate(url)) {
                builder = MockMvcRequestBuilders.options(url, convertJsonToArray(params));
            } else {
                builder = MockMvcRequestBuilders.options(url)
                        // set request param
                        .params(convertJsonToMap(params));
            }
            RequestBuilder requestBuilder = buildHeaderAuthentication(builder)
                    // set request content type
                    .contentType(formData());
            String result = performMockMvc(requestBuilder).getContentAsString();
            doResult(result);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * options with payload data
     *
     * @param url
     * @param param
     */
    public void optionsBody(String url, String param) {

        try {
            RequestBuilder requestBuilder = buildHeaderAuthentication(MockMvcRequestBuilders.options(url))
                    // 设置body内容
                    .content(param)
                    // set request content type
                    .contentType(payload());
            String result = performMockMvc(requestBuilder).getContentAsString();
            doResult(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * head with form-data data
     *
     * @param url
     * @param params
     */
    public void head(String url, String params) {
        try {
            RequestBuilder requestBuilder = buildHeaderAuthentication(MockMvcRequestBuilders.head(url))
                    // set request param
                    .params(convertJsonToMap(params))
                    // set request content type
                    .contentType(formData());
            String result = performMockMvc(requestBuilder).getContentAsString();
            doResult(result);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    /**
     * request with form-data data, like post method
     *
     * @param url
     * @param params
     */
    public void request(String url, String params) {
        request(url, params, HttpMethod.POST);
    }


    /**
     * request with form-data data
     *
     * @param url
     * @param params
     * @param httpMethod
     */
    public void request(String url, String params, HttpMethod httpMethod) {
        try {
            MockHttpServletRequestBuilder builder;
            // PathVariable
            if (isUrlTemplate(url)) {
                builder = MockMvcRequestBuilders.request(httpMethod, url, convertJsonToArray(params));
            } else {
                builder = MockMvcRequestBuilders.request(httpMethod, url)
                        // set request param
                        .params(convertJsonToMap(params));
            }
            RequestBuilder requestBuilder = buildHeaderAuthentication(builder)
                    // set request content type
                    .contentType(formData());
            String result = performMockMvc(requestBuilder).getContentAsString();
            doResult(result);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * request with payload data, like post method
     *
     * @param url
     * @param params
     */
    public void requestBody(String url, String params) {
        requestBody(url, params, HttpMethod.POST);
    }

    /**
     * request with payload
     *
     * @param url
     * @param param
     * @param httpMethod
     */
    public void requestBody(String url, String param, HttpMethod httpMethod) {

        try {
            RequestBuilder requestBuilder = buildHeaderAuthentication(MockMvcRequestBuilders.request(httpMethod, url))
                    // 设置body内容
                    .content(param)
                    // set request content type
                    .contentType(payload());
            String result = performMockMvc(requestBuilder).getContentAsString();
            doResult(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * upload file
     *
     * @param url
     */
    public void file(String url) {
        try {
            RequestBuilder requestBuilder = MockMvcRequestBuilders.multipart(url)
                    .file(new MockMultipartFile("file", "test.txt",
                            "multipart/form-data",
                            "test upload".getBytes("UTF-8")));
            String result = performMockMvc(requestBuilder).getContentAsString();
            doResult(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * set head authentication info
     * @param builder
     * @return
     */
    MockHttpServletRequestBuilder buildHeaderAuthentication(MockHttpServletRequestBuilder builder) {
        if (AnerTesterConfig.me().isLogin()) {
            return builder.header(AnerTesterConfig.me().getHeader(), AnerTesterConfig.me().getTestToken());
        } else {
            return builder;
        }
    }

    /**
     * build response
     *
     * @param requestBuilder
     * @return
     * @throws Exception
     */
    private MockHttpServletResponse performMockMvc(RequestBuilder requestBuilder) throws Exception {
        MockHttpServletResponse response =  mockMvc.perform(requestBuilder)
                .andExpect(status().isOk())
                .andReturn()
                .getResponse();
        response.setContentType(Constant.CONTENT_TYPE);
        return response;
    }

    /**
     * convert Json string to map
     *
     * @param jsonStr
     * @return
     */
    private MultiValueMap convertJsonToMap(String jsonStr) {
        JSONObject jsonObject = (JSONObject) JSON.parse(jsonStr);
        MultiValueMap paramMap = new LinkedMultiValueMap<>();
        jsonObject.entrySet().stream().forEach(
                entry -> {
                    if (entry.getValue() instanceof JSONArray) {
                        JSONArray jsonArray = (JSONArray) entry.getValue();
                        for(Object object: jsonArray) {
                            paramMap.add(entry.getKey(), object.toString());
                        }
                    } else {
                        paramMap.add(entry.getKey(), entry.getValue().toString());
                    }

                }
        );
        return paramMap;
    }

    /**
     * convert Json string to list
     *
     * @param jsonStr
     * @return
     */
    private Object[] convertJsonToArray(String jsonStr) {
        JSONObject jsonObject = (JSONObject) JSON.parse(jsonStr, Feature.OrderedField);
        List paramList = new ArrayList<>();
        jsonObject.entrySet().stream().forEach(
                entry -> {
                    if (entry.getValue() instanceof JSONArray) {
                        paramList.add(((JSONArray) entry.getValue()).stream().map(ele -> String.valueOf(ele)).collect(Collectors.joining(",")));
                    } else {
                        paramList.add(entry.getValue().toString());
                    }
                }
        );
        return paramList.toArray();
    }


    private String formData() {
        String formData = AnerTesterConfig.me().getContentTypeFormData();
        if (StringUtils.isNotEmpty(formData)) {
            return formData;
        } else {
            return Constant.REQUEST_CONTENT_TYPE_FORM_DATA;
        }
    }

    private String payload() {
        String payload = AnerTesterConfig.me().getContentTypePayload();
        if (StringUtils.isNotEmpty(payload)) {
            return payload;
        } else {
            return Constant.REQUEST_CONTENT_TYPE_PAYLOAD;
        }
    }


    public void doResult(String result) {
        logger.info("测试结果返回值：{}", result);
    }

    public boolean isUrlTemplate(String url) {
        return url.contains("{") && url.contains("}");
    }
}
